// $ sudo apt install libglew-dev
#include <GL/glew.h>

// $ sudo apt install libglfw3-dev
#include <GLFW/glfw3.h>

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

#define numVAOS 1
GLuint renderingProgram;
GLuint vao[numVAOS];

float x = 0.0f;
float inc = 0.01f;

string readShaderSource(const char *filePath) {
    string content;
    ifstream fileStream(filePath, ios::in);
    string line = "";

    while(!fileStream.eof()) {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    cout << "readed content: " << endl << "-------------------------------\n" << content << endl;

    return content;
}

void printShaderLog(GLuint shader){
    int len = 0;
    int chWrittn = 0;
    char *log;

    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
    if(len > 0) {
        log = (char *)malloc(len);
        glGetShaderInfoLog(shader, len, &chWrittn, log);
        cout << "Shader Info Log: " << log << endl;
        free(log);
    }
}

void printProgramLog(int prog){
    int len = 0;
    int chWrittn = 0;
    char *log = NULL;

    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
    if(len > 0) {
        log = (char *)malloc(len);
        glGetProgramInfoLog(prog, len, &chWrittn, log);
        cout << "Program Info Log: " << log << endl;
        free(log);
    }
}

bool checkOpenGLError() {
    bool foundError = false;
    int glErr = glGetError();

    while(glErr != GL_NO_ERROR) {
        cout << "glError: " << glErr << endl;
        foundError = true;
        glErr = glGetError();
    }

    return foundError;
}

GLuint createShaderProgram(){
    GLint vertCompiled;
    GLint fragCompiled;
    GLint linked;

    string vertShaderString = readShaderSource("../vertShader.glsl");
    string fragShaderString = readShaderSource("../fragShader.glsl");

    const char *vShaderSource = vertShaderString.c_str();
    const char *fShaderSource = fragShaderString.c_str();

    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vShader, 1, &vShaderSource, NULL);
    glShaderSource(fShader, 1, &fShaderSource, NULL);
    
    glCompileShader(vShader);
    checkOpenGLError();
    glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
    if(vertCompiled != 1) {
        cout << "vertex compilation failed" << endl;
        printShaderLog(vShader);
    }

    glCompileShader(fShader);
    checkOpenGLError();
    glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
    if(fragCompiled != 1) {
        cout << "fragment compilation failed" << endl;
        printShaderLog(fShader);
    }

    GLuint vfProgram = glCreateProgram();
    glAttachShader(vfProgram, vShader);
    glAttachShader(vfProgram, fShader);
    glLinkProgram(vfProgram);
    checkOpenGLError();
    glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
    if(linked != 1) {
        cout << "linking failed" << endl;
        printProgramLog(vfProgram);
    }

    return vfProgram;
}

void init(GLFWwindow *window) {
    renderingProgram = createShaderProgram();
    glGenVertexArrays(numVAOS, vao);
    glBindVertexArray(vao[0]);
}

void display(GLFWwindow *window, double currentTime){
    glClear(GL_DEPTH_BUFFER_BIT);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(renderingProgram);
    x += inc;

    if(x > 1.0f) {
        inc = -0.01f;
    } else if(x < -1.0f) {
        inc = 0.01f;
    }
    GLuint offsetLoc = glGetUniformLocation(renderingProgram, "offset");

    glProgramUniform1f(renderingProgram, offsetLoc, x);

    glDrawArrays(GL_TRIANGLES, 0, 3);
}

int main(int, char**) {
    int ret = 0;

    std::cout << "Hello, world!\n";
    if(!(ret = glfwInit())) {
        cout << "glfwInit(1): " << ret << endl;
        exit(EXIT_FAILURE);
    }
    // 要求机器必须兼容OpenGL 4.3的版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // 创建glfw窗口
    GLFWwindow *window = glfwCreateWindow(600, 600, "Chapter2 - program1", NULL, NULL);
    // 与当前OpenGL上下文关联起来
    glfwMakeContextCurrent(window);

    // 注意这里是glewInit(), 不是glfwInit()
    if((ret = glewInit()) != GLEW_OK) {
        cout << "glfwInit(2): " << ret << endl;
        exit(EXIT_FAILURE);
    }
    // 只要有1个buffer交换就刷新
    glfwSwapInterval(1);

    init(window);

    while(!glfwWindowShouldClose(window)) {
        display(window, glfwGetTime());
        // 垂直同步
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwDestroyWindow(window);
    glfwTerminate();

    return EXIT_SUCCESS;
}